/*
 *  network.c
 *  Rokumote
 *
 *  Created by John Roark on 10/6/09.
 *  Copyright 2009 __MyCompanyName__. All rights reserved.
 *
 */

#include "network.h"

#include <sys/param.h>
#include <sys/file.h>
#include <sys/socket.h>
#include <sys/sockio.h>
#include <sys/sysctl.h>
#include <sys/ioctl.h>
#include <sys/time.h>
#include <net/if.h>
#include <net/if_dl.h>
#include <netinet/in.h>
#include <arpa/inet.h>

#include <err.h>
#include <errno.h>
#include <netdb.h>
#include <paths.h>
#include <stdio.h>
#include <stdlib.h>
#include <strings.h>
#include <unistd.h>

#include <sys/types.h>
#include <sys/socket.h>
#include <net/ethernet.h>
#include <ifaddrs.h>

#define	ROKUPORT	8080
#define	ROKU_HWADDR	"\x00\x0D\x4B"
#define	IFT_ETHER   0x06
#define RTF_LLINFO	0x400		/* generated by link layer (e.g. ARP) */

#define ROUNDUP(a) \
((a) > 0 ? (1 + (((a) - 1) | (sizeof(long) - 1))) : sizeof(long))

/*
 * These numbers are used by reliable protocols for determining
 * retransmission behavior and are included in the routing structure.
 */
struct rt_metrics {
	u_int32_t	rmx_locks;	/* Kernel must leave these values alone */
	u_int32_t	rmx_mtu;	/* MTU for this path */
	u_int32_t	rmx_hopcount;	/* max hops expected */
	int32_t		rmx_expire;	/* lifetime for route, e.g. redirect */
	u_int32_t	rmx_recvpipe;	/* inbound delay-bandwidth product */
	u_int32_t	rmx_sendpipe;	/* outbound delay-bandwidth product */
	u_int32_t	rmx_ssthresh;	/* outbound gateway buffer limit */
	u_int32_t	rmx_rtt;	/* estimated round trip time */
	u_int32_t	rmx_rttvar;	/* estimated rtt variance */
	u_int32_t	rmx_pksent;	/* packets sent using this route */
	u_int32_t	rmx_filler[4];	/* will be used for T/TCP later */
};

/*
 * Structures for routing messages.
 */
struct rt_msghdr {
	u_short	rtm_msglen;		/* to skip over non-understood messages */
	u_char	rtm_version;		/* future binary compatibility */
	u_char	rtm_type;		/* message type */
	u_short	rtm_index;		/* index for associated ifp */
	int	rtm_flags;		/* flags, incl. kern & message, e.g. DONE */
	int	rtm_addrs;		/* bitmask identifying sockaddrs in msg */
	pid_t	rtm_pid;		/* identify sender */
	int	rtm_seq;		/* for sender to identify action */
	int	rtm_errno;		/* why failed */
	int	rtm_use;		/* from rtentry */
	u_int32_t rtm_inits;		/* which metrics we are initializing */
	struct rt_metrics rtm_rmx;	/* metrics themselves */
};

struct sockaddr_inarp {
	u_char	sin_len;
	u_char	sin_family;
	u_short sin_port;
	struct	in_addr sin_addr;
	struct	in_addr sin_srcaddr;
	u_short	sin_tos;
	u_short	sin_other;
#define SIN_PROXY 1
};

unsigned long g_ip = 0;

int search(u_long addr, int (*action)(struct sockaddr_dl *sdl,
									  struct sockaddr_inarp *sin, struct rt_msghdr *rtm));

unsigned long get_ip ()
{
	struct ifaddrs		*addrs;
	struct ifaddrs		*addr;
	unsigned long		ret	= 0;
	
	if (getifaddrs (&addrs) != -1) {
		addr = addrs;
		while (addr != NULL)
		{
			if(!strncmp("en0", addr->ifa_name, 3) && (addr->ifa_addr->sa_family == AF_INET))
			{
				ret = ((struct sockaddr_in *)addr->ifa_addr)->sin_addr.s_addr;
				break;
			}
			addr = addr->ifa_next;
		}
		freeifaddrs (addrs);
	}
	
	return ret;
}

int
is_rocku(struct sockaddr_dl *sdl,
		 struct sockaddr_inarp *addr, struct rt_msghdr *rtm)
{
	int rtn = 0;
	
	if ((sdl->sdl_type == IFT_ETHER) && (sdl->sdl_alen)) {
        char *cp;
		char *hw = ROKU_HWADDR;
		
        cp = (char *)LLADDR(sdl);
		
		if (memcmp (cp, hw, 3) == 0) {
			int	s = -1;
			struct sockaddr_in	s_addr;
			
			s_addr.sin_family = AF_INET;
			s_addr.sin_port = htons (ROKUPORT);
			s_addr.sin_addr = addr->sin_addr;
			
			s = socket (AF_INET, SOCK_STREAM, IPPROTO_TCP);
			
			if (s > 0) {
				if (connect (s, (struct sockaddr *)&s_addr, sizeof (s_addr)) >= 0) {
					rtn = 1;
					g_ip = addr->sin_addr.s_addr;
				}
				
				shutdown (s, 2);
				close (s);
			}
		}
	}
	
	return rtn;
}

unsigned long find_roku (/*char *ip*/)
{
	struct sockaddr_in	s_addr	= {0};
	int					s		= 0;
	int					i		= 0;
	struct in_addr		addr	= {0};
	
	addr.s_addr = get_ip();
	memcpy (&s_addr.sin_addr, &addr, sizeof(s_addr.sin_addr));
	
	s_addr.sin_family = AF_INET;
	s_addr.sin_port = htons(9);
	s_addr.sin_addr.s_addr &= 0x00FFFFFF;
	
	for (i = 0; i <= 255; i++) {
		s_addr.sin_addr.s_addr += 0x01000000;
		
		if (!search (s_addr.sin_addr.s_addr, is_rocku)) {
			/* add this ip to the arp table and search again */
			s = socket (AF_INET, SOCK_DGRAM, IPPROTO_UDP);
			
			if (s > 0) {
				sendto (s, "roku?", 5, 0, (const struct sockaddr *)&s_addr, sizeof(s_addr));
				shutdown (s, 2);
				close (s);
			}
			
			usleep (25000);
			search (s_addr.sin_addr.s_addr, is_rocku);
		}
		
	}
	
	return g_ip;
}

/*
 * Search the arp table and do some action on matching entries
 */
int
search(u_long addr, int (*action)(struct sockaddr_dl *sdl,
								  struct sockaddr_inarp *sin,
								  struct rt_msghdr *rtm))
{
	int mib[6];
	size_t needed;
	char *lim, *buf, *next;
	struct rt_msghdr *rtm;
	struct sockaddr_inarp *sin2;
	struct sockaddr_dl *sdl;
	int rtn = 0;
	
	mib[0] = CTL_NET;
	mib[1] = PF_ROUTE;
	mib[2] = 0;
	mib[3] = AF_INET;
	mib[4] = NET_RT_FLAGS;
	mib[5] = RTF_LLINFO;
	if (sysctl(mib, 6, NULL, &needed, NULL, 0) >= 0) {
		if ((buf = malloc(needed)) != NULL) {
			if (sysctl(mib, 6, buf, &needed, NULL, 0) >= 0) {
				lim = buf + needed;
				for (next = buf; next < lim; next += rtm->rtm_msglen) {
					rtm = (struct rt_msghdr *)next;
					sin2 = (struct sockaddr_inarp *)(rtm + 1);
					sdl = (struct sockaddr_dl*)((char*)sin2 + ROUNDUP(sin2->sin_len));
					if (addr) {
						if (addr != sin2->sin_addr.s_addr)
							continue;
						rtn = 1;
					}
					/*rtn =*/ (*action)(sdl, sin2, rtm);
				}
			}
			free(buf);
		}
	}
	
	return rtn;
}

int send_command (const char *server, const char *pszCommand)
{
	struct sockaddr_in s_addr;
	int s;
	struct hostent *hp;
	
	s = socket (AF_INET, SOCK_STREAM, 0);
	if (s < 0) {
		// opening stream socket
		return -1;
	} 
	
	s_addr.sin_family = AF_INET;
	hp = gethostbyname (server);
	if (hp == 0) {
		// unknown host
		return -2;
	} 
	
	memcpy ((char *)&s_addr.sin_addr, (char*)hp->h_addr, hp->h_length);
	s_addr.sin_port = htons(ROKUPORT); 
	
	if (connect (s, (struct sockaddr *)&s_addr, sizeof (s_addr)) < 0) {
		// Connecting stream socket
		return -3;
	}

	send (s, pszCommand, strlen (pszCommand), 0);
	send (s, "\n", 1, 0);
	
	shutdown (s, 2);
	close (s);
	
    return 0;
	
}


